<!--
 * 级联选择器
 * @作者: 陈老师儿~
 * @手机: 18560000860
 -->
<template>
    <view class="pure-cascader" :class="classes" :style="styles">
        <!-- 使用 pure-popup 组件，在支付宝小程序中 swiper 高度会塌陷为0，只能自行实现弹窗了 -->
        <!-- Overlay -->
        <pure-overlay :show="show && overlay" @close="handleOverlayClose"></pure-overlay>

        <view class="pure-cascader-wrapper">
            <!-- Header -->
            <view class="pure-cascader-header">
                <!-- Title -->
                <view class="pure-cascader-title">
                    <slot name="title" v-if="$slots.title"></slot>
                    <text v-else>{{ title }}</text>
                </view>

                <!-- Close -->
                <view class="pure-cascader-close" v-if="close" :hover-clas="hoverClass" @tap="handleClose">
                    <slot name="close" v-if="$slots.close"></slot>
                    <pure-icon :name="closeIcon" v-else></pure-icon>
                </view>
            </view>

            <view class="pure-cascader-tabs-wrapper">
                <!-- 抖音小程序和飞书小程序中组件套组件会让 provide/inject 失效 -->
                <!-- #ifndef MP-TOUTIAO || MP-LARK -->
                <pure-tabs :value="swiperCurrent" @tabClick="handleTabClick" ref="PureTabsRef" :animation="false">
                    <pure-tabs-item v-for="(_tab, _index) in tabs" :key="_tab.value" :value="_tab.value" :item="_tab">
                        <text class="pure-cascader-tab" :class="getTabClasses(_tab)">{{ getTabText(_tab, _index) }}</text>
                    </pure-tabs-item>
                </pure-tabs>
                <!-- #endif -->

                <!-- #ifdef MP-TOUTIAO || MP-LARK -->
                <scroll-view class="pure-cascader-tabs" :scroll-x="true" :scroll-y="false" @scroll="handleTabsScroll" :id="tabsScrollViewId" :scroll-left="bindTabsScrollLeft">
                    <view class="pure-cascader-tabs-list">
                        <view class="pure-cascader-tab" :class="getTabClasses(_tab, _index)" v-for="(_tab, _index) in tabs" :key="_tab.value" @tap="handleTabClick(_tab, _index)" :id="tabId + _index">
                            {{ getTabText(_tab, _index) }}
                        </view>
                    </view>
                </scroll-view>
                <!-- #endif -->
            </view>

            <!-- Body -->
            <view class="pure-cascader-body">
                <swiper class="pure-cascader-swiper" :current="swiperCurrent" @change="handleSwiperChange">
                    <!-- Swiper Item -->
                    <swiper-item class="pure-cascader-swiper-item" v-for="(_tab, _index) in tabs" :key="_tab.value">
                        <view class="pure-cascader-options-body">
                            <scroll-view class="pure-cascader-scroll-view" :scroll-x="false" :scroll-y="true">
                                <view class="pure-cascader-options-title" v-if="getTitle(_index)">{{ getTitle(_index) }} </view>
                                <!-- Options -->
                                <view class="pure-cascader-options">
                                    <!-- Option -->
                                    <view
                                        class="pure-cascader-option"
                                        v-for="(_item, _current) in _tab.options"
                                        :key="_item?.[labelKey]"
                                        :class="optionClasses(_tab, _index, _current, _item)"
                                        @tap="handleOptionClick(_tab, _index, _current, _item)"
                                    >
                                        <text class="pure-cascader-option-text">{{ _item?.[labelKey] }}</text>
                                        <view class="pure-cascader-option-icon">
                                            <pure-icon :name="selectedIcon"></pure-icon>
                                        </view>
                                    </view>
                                </view>
                            </scroll-view>
                        </view>
                    </swiper-item>
                </swiper>
            </view>

            <!-- Footer -->
            <view class="pure-cascader-footer">
                <slot name="footer" v-if="$slots.footer"></slot>
            </view>
        </view>
    </view>
</template>

<script>
    import props from "./props.js";
    export default {
        options: {
            // 将自定义节点设置成虚拟的
            // 头条、飞书：因为合并后丢失事件和 'provide/inject'
            // 京东：因为合并后不能合并外层 'class'
            // 百度：因为合并后会在控制台疯狂报警告
            // #ifndef MP-TOUTIAO || MP-LARK || MP-JD || MP-BAIDU
            virtualHost: true,
            // #endif

            // 组件样式隔离
            styleIsolation: "apply-shared",

            // 允许使用多个插槽
            multipleSlots: true,
        },
        props: props,
        emits: ["confirm", "select", "close"],
        data() {
            return {
                // IDs
                tabsScrollViewId: "",
                tabId: "",
                // 选中项的索引
                indexes: [],
                // Tabs
                tabs: [],
                // Swiper Current
                swiperCurrent: -1,
                // 显示状态
                status: "hide",
                // Tabs
                tabsScrollLeft: 0,
                bindTabsScrollLeft: 0,
            };
        },
        computed: {
            // classes
            classes() {
                const classes = [];

                // 显示状态类名
                classes.push(`pure-cascader-${this.status}`);

                // 显示安全区
                if (this.safeBottom) classes.push("pure-cascader-safe-bottom");

                return classes;
            },

            // styles
            styles() {
                const styles = [];
                return styles;
            },
        },
        watch: {
            // 监听数据
            options: {
                handler() {
                    // 更新Tabs
                    this.updateTabs();
                },
                deep: true,
            },

            // 监听显示状态
            async show(val) {
                if (val) {
                    this.status = "before-show";
                    await this.$nextTick();
                    await this.sleep(20);
                    this.status = "to-show";

                    // 显示时需重新初始化一下 pure-tabs
                    // 这是因为隐藏是设置了 display: none，导致 pure-tabs 不能正常设置指示器的位置
                    await this.$nextTick();
                    await this.sleep(200);
                    this.$refs.PureTabsRef?.init?.();
                } else {
                    this.status = "before-hide";
                    await this.$nextTick();
                    await this.sleep(20);
                    this.status = "to-hide";
                }
            },

            // 抖音、飞书小程序
            swiperCurrent() {
                // #ifdef MP-TOUTIAO || MP-LARK
                // 更新激活项位置
                this.updateActiveTabPosition();
                // #endif
            },
        },
        async created() {
            // 初始化Tabs
            this.initTabs();

            // 设置默认Swiper Current
            await this.$nextTick();
            await this.sleep();
            this.swiperCurrent = this.tabs.length - 1;
        },
        async mounted() {
            // 初始化IDs
            this.initIDs();

            // 初始化状态
            this.status = this.show ? "show" : "hide";

            // 显示时需重新初始化一下 pure-tabs
            // 这是因为隐藏是设置了 display: none，导致 pure-tabs 不能正常设置指示器的位置
            await this.$nextTick();
            await this.sleep(200);
            this.$refs.PureTabsRef?.init?.();
        },
        methods: {
            // 初始化IDs
            initIDs() {
                this.tabsScrollViewId = `PureCascaderTabsScrollView_${Date.now()}_${this.randomString()}`;
                this.tabId = `PureCascaderTab_${Date.now()}_${this.randomString()}`;
            },

            // 初始化Tabs
            initTabs() {
                // 设置了默认选中值
                if (this.defaultIndexes?.length) {
                    this.defaultIndexes.forEach((item, index) => {
                        const tab = this.createTab(index);
                        tab.selectedIndex = item;
                        this.tabs.push(tab);
                    });

                    // 提交确认事件
                    this.$emit("confirm", this.getIndexes(), this.getSelectedItems());
                    return;
                }

                this.tabs = [this.createTab()];
            },

            // 更新Tabs
            // 用于异步数据加载后更新Tabs
            async updateTabs() {
                // 找到选中了选项 && 有子选项 && 后个Tab为空的 Tab
                // 找到了，说明是异步加载的数据，需要创建新的 Tab
                const findIndex = this.tabs.findIndex((tab, index) => {
                    const selectedIndex = tab.selectedIndex;
                    const options = tab.options;
                    const selectedOption = options[selectedIndex];
                    const children = selectedOption?.[this.childrenKey];
                    const nextTab = this.tabs[index + 1];
                    return selectedIndex >= 0 && !!children && !nextTab;
                });

                // 找到了
                if (findIndex >= 0) {
                    // 创建Tab
                    this.tabs.push(this.createTab(findIndex + 1));

                    // 更新Swiper Current
                    // 延时一下会有切换效果，否则动态添加的 swiper item 没有切换效果
                    await this.$nextTick();
                    await this.sleep(20);
                    this.swiperCurrent = findIndex + 1;
                }
            },

            // 创建默认Tab
            createTab(index = 0) {
                if (index === 0) {
                    return {
                        label: "请选择",
                        value: index,
                        selectedIndex: null,
                        options: this.options,
                    };
                }

                // 前一个Tab
                const parentTab = this.tabs[index - 1];
                const parentTabSelectedIndex = parentTab.selectedIndex;
                const parentTabOptions = parentTab.options;

                return {
                    label: "请选择",
                    value: index,
                    selectedIndex: null,
                    options: parentTabOptions[parentTabSelectedIndex]?.[this.childrenKey] || [],
                };
            },

            // 选项点击事件
            async handleOptionClick(tab, tabIndex, optionIndex, option) {
                // 禁用了
                if (option?.disabled) return;

                // 更新选中值
                tab.selectedIndex = optionIndex;

                // 清除点击级别之后的所有下级
                this.tabs.splice(tabIndex + 1);

                // 提交选择事件
                this.$emit("select", this.getIndexes(), tabIndex, optionIndex, tab.options[optionIndex]);

                // 还有下级
                if (tab.options[optionIndex]?.[this.childrenKey]?.length) {
                    // 创建下级Tab
                    const childTab = this.createTab(tabIndex + 1);

                    // 添加到Tabs
                    this.tabs.push(childTab);

                    // 更新Swiper Current
                    // 延时一下会有切换效果，否则动态添加的 swiper item 没有切换效果
                    await this.$nextTick();
                    await this.sleep(20);
                    this.swiperCurrent = this.tabs.length - 1;

                    return;
                }

                // 没有下级
                // 提交确认事件
                this.$emit("confirm", this.getIndexes(), this.getSelectedItems());
            },

            // 获取选中索引值
            getIndexes() {
                const indexes = [];

                this.tabs.forEach((item) => {
                    indexes.push(item.selectedIndex);
                });

                return indexes;
            },

            // 获取选中项数据
            getSelectedItems() {
                const items = [];

                this.tabs.forEach((item) => {
                    items.push(item.options[item.selectedIndex]);
                });

                return items;
            },

            // Tab显示文本
            getTabText(tab, index) {
                if (tab.selectedIndex === null || tab.selectedIndex === undefined || tab.selectedIndex < 0) {
                    // 定义了格式化函数
                    if (typeof this.placeholder === "function") {
                        return this.placeholder(index);
                    }

                    // 是数组
                    if (Array.isArray(this.placeholder)) {
                        return this.placeholder[index];
                    }

                    // 默认则认为是字符串
                    return this.placeholder;
                }

                return tab.options[tab.selectedIndex]?.[this.labelKey];
            },

            // 选项类名
            optionClasses(tab, tabIndex, optionIndex, option) {
                const classes = [];

                // 是否选中
                if (tab.selectedIndex === optionIndex) classes.push("pure-cascader-option-selected");

                // 是否禁用
                if (option?.disabled) classes.push(this.disabledClass);

                // 隐藏项
                if (option?.hide) classes.push("pure-cascader-option-hide");

                return classes;
            },

            // Tab 类名
            getTabClasses(tab, index) {
                const classes = [];

                if (tab.selectedIndex === null || tab.selectedIndex === undefined || tab.selectedIndex < 0) classes.push("pure-cascader-tab-placeholder");

                if (index === this.swiperCurrent) classes.push("pure-cascader-tab-active");

                return classes;
            },

            // 左右切换
            handleSwiperChange(e) {
                this.swiperCurrent = e.detail.current;
            },

            // Tab点击事件
            handleTabClick(item) {
                // 更新 Swiper Current
                this.swiperCurrent = item.value;
            },

            // 关闭事件
            handleClose() {
                this.$emit("close");
            },

            // 显示的标题
            getTitle(index) {
                // 自定义了格式化函数
                if (typeof this.titles === "function") {
                    return this.titles(index);
                }

                // 自定义了 titles
                if (this.titles) {
                    // 数组类型
                    if (Array.isArray(this.titles)) return this.titles[index];
                    // 字符串类型
                    if (typeof this.titles === "string") return this.titles;
                }

                // 否则返回空
                return "";
            },

            // 遮罩点击事件
            handleOverlayClose() {
                // 点击遮罩不可关闭
                if (this.stopCloseOnClickOverlay) return;
                this.$emit("close");
            },

            // 滚动事件
            handleTabsScroll(e) {
                this.tabsScrollLeft = e.detail.scrollLeft;
            },

            // 更新激活项位置
            async updateActiveTabPosition() {
                const id = `#${this.tabId + this.swiperCurrent}`;

                // 等待一下
                await this.$nextTick();
                await this.sleep(200);

                // 获取元素的布局信息
                const svRect = await this.getScrollViewRect();

                // 获取元素的布局信息
                const tabRect = await this.getTabRect(id);

                // 没获取到
                if (!svRect || !tabRect) return;

                // 判断是否在可是范围
                // 先判断右侧
                if (tabRect.right > svRect.right) {
                    // 右侧超出了可视范围
                    this.bindTabsScrollLeft = this.tabsScrollLeft + tabRect.right - svRect.right;

                    // 有时点击的快了，会不准确，所以再修正一下，直到位置准确
                    this.updateActiveTabPosition();
                } else if (tabRect.left < svRect.left) {
                    // 左侧超出了可视范围
                    this.bindTabsScrollLeft = this.tabsScrollLeft + tabRect.left - svRect.left;

                    // 有时点击的快了，会不准确，所以再修正一下，直到位置准确
                    this.updateActiveTabPosition();
                }
            },

            // 获取滚动视图的布局信息
            getScrollViewRect() {
                return new Promise(async (resolve) => {
                    const id = `#${this.tabsScrollViewId}`;
                    const query = uni.createSelectorQuery().in(this);
                    query
                        .select(id)
                        .boundingClientRect((rect) => {
                            resolve(rect);
                        })
                        .exec();
                });
            },

            // 获取元素的布局信息
            getTabRect(id) {
                return new Promise(async (resolve) => {
                    const query = uni.createSelectorQuery().in(this);
                    query
                        .select(id)
                        .boundingClientRect((rect) => {
                            resolve(rect);
                        })
                        .exec();
                });
            },

            // 同步延时
            sleep(time = 50) {
                return new Promise((resolve) => {
                    let timer = setTimeout(() => {
                        clearTimeout(timer);
                        resolve();
                    }, time);
                });
            },

            // 生成一个36位的随机字符串
            randomString() {
                return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
            },
        },
    };
</script>

<style scoped>
    @import "./style.css";
</style>
